package com.itchen.school.common.jdkext;


import com.google.gson.Gson;
import com.itchen.school.common.exception.SysException;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nonnull;

public class ChainMap<K, V> {
    private final Map<K, V> _map;

    public ChainMap() {
        this(new HashMap());
    }

    public ChainMap(@Nonnull Map<K, V> jMap) {
        this._map = jMap;
    }

    public ChainMap<K, V> put(K key, V value) {
        this._map.put(key, value);
        return this;
    }

    public ChainMap<K, V> putNonNull(K key, V value) {
        if (key != null && value != null) {
            this._map.put(key, value);
        }

        return this;
    }

    public ChainMap<K, V> putAll(Map<? extends K, ? extends V> m) {
        this._map.putAll(m);
        return this;
    }

    public ChainMap<K, V> remove(K key) {
        this._map.remove(key);
        return this;
    }

    public ChainMap<K, V> remove(K key, V value) {
        this._map.remove(key, value);
        return this;
    }

    public ChainMap<K, V> clear() {
        this._map.clear();
        return this;
    }

    public ChainMap<K, V> putIfAbsent(K key, V value) {
        this._map.putIfAbsent(key, value);
        return this;
    }

    public ChainMap<K, V> replace(K key, V oldValue, V newValue) {
        this._map.replace(key, oldValue, newValue);
        return this;
    }

    public ChainMap<K, V> replace(K key, V newValue) {
        this._map.replace(key, newValue);
        return this;
    }

    public ChainMap<K, V> replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        this._map.replaceAll(function);
        return this;
    }

    public ChainMap<K, V> computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        this._map.computeIfAbsent(key, mappingFunction);
        return this;
    }

    public ChainMap<K, V> compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        this._map.compute(key, remappingFunction);
        return this;
    }

    public ChainMap<K, V> computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        this._map.computeIfPresent(key, remappingFunction);
        return this;
    }

    public ChainMap<K, V> merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        this._map.merge(key, value, remappingFunction);
        return this;
    }

    public ChainMap<K, V> forEach(BiConsumer<? super K, ? super V> action) {
        this._map.forEach(action);
        return this;
    }

    public ChainMap<K, V> consume(K key, @Nonnull Consumer<? super V> action) {
        action.accept(this._map.get(key));
        return this;
    }

    public ChainMap<K, V> consumeIfPresent(K key, @Nonnull Consumer<? super V> action) {
        if (this._map.containsKey(key)) {
            action.accept(this._map.get(key));
        }

        return this;
    }

    public ChainMap<K, V> forEachKey(Consumer<? super K> keyAction) {
        this._map.keySet().forEach(keyAction);
        return this;
    }

    public ChainMap<K, V> forEachValues(Consumer<? super V> valueAction) {
        this._map.values().forEach(valueAction);
        return this;
    }

    public int size() {
        return this._map.size();
    }

    public boolean isEmpty() {
        return this._map.isEmpty();
    }

    public V get(K key) {
        return this._map.get(key);
    }

    public V getOrDefault(K key, V defaultValue) {
        return this._map.getOrDefault(key, defaultValue);
    }

    public boolean containsKey(K key) {
        return this._map.containsKey(key);
    }

    public boolean containsValue(V value) {
        return this._map.containsValue(value);
    }

    public Set<K> keySet() {
        return this._map.keySet();
    }

    public Collection<V> values() {
        return this._map.values();
    }

    public Set<Entry<K, V>> entrySet() {
        return this._map.entrySet();
    }

    public Map<K, V> map() {
        return this._map;
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k, XV v) {
        return (new ChainMap()).putNonNull(k, v);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4, XK k5, XV v5) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4).putNonNull(k5, v5);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4, XK k5, XV v5, XK k6, XV v6) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4).putNonNull(k5, v5).putNonNull(k6, v6);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4, XK k5, XV v5, XK k6, XV v6, XK k7, XV v7) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4).putNonNull(k5, v5).putNonNull(k6, v6).putNonNull(k7, v7);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4, XK k5, XV v5, XK k6, XV v6, XK k7, XV v7, XK k8, XV v8) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4).putNonNull(k5, v5).putNonNull(k6, v6).putNonNull(k7, v7).putNonNull(k8, v8);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4, XK k5, XV v5, XK k6, XV v6, XK k7, XV v7, XK k8, XV v8, XK k9, XV v9) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4).putNonNull(k5, v5).putNonNull(k6, v6).putNonNull(k7, v7).putNonNull(k8, v8).putNonNull(k9, v9);
    }

    public static <XK, XV> ChainMap<XK, XV> of(XK k1, XV v1, XK k2, XV v2, XK k3, XV v3, XK k4, XV v4, XK k5, XV v5, XK k6, XV v6, XK k7, XV v7, XK k8, XV v8, XK k9, XV v9, XK k10, XV v10) {
        return (new ChainMap()).putNonNull(k1, v1).putNonNull(k2, v2).putNonNull(k3, v3).putNonNull(k4, v4).putNonNull(k5, v5).putNonNull(k6, v6).putNonNull(k7, v7).putNonNull(k8, v8).putNonNull(k9, v9).putNonNull(k10, v10);
    }

    public static <XK, XV> ChainMap<XK, XV> ofX(Object... kvs) {
        if (kvs != null && kvs.length % 2 != 0) {
            throw new SysException("Keys and values do not correspond one to one.");
        } else {
            ChainMap<XK, XV> map = new ChainMap();
            if (kvs == null) {
                return map;
            } else {
                for(int i = 0; i < kvs.length; i += 2) {
                    XK key = (XK) kvs[i];
                    XV value = (XV) kvs[i + 1];
                    if (key != null && value != null) {
                        map.put(key, value);
                    }
                }

                return map;
            }
        }
    }

    public String toString() {
        return (new Gson()).toJson(this._map);
    }
}

